package com.codegeek.project.communicate;

import com.codegeek.common.utils.myStringUtils;
import com.codegeek.project.slope.service.ISlopeGlareService;
import org.apache.logging.log4j.LogManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author wj
 * @ClassName
 * @Description
 * @date 2020-03-21
 */
@Component
@EnableScheduling
public class SlopeDTUService implements ApplicationRunner {
    private final int port = 9500;                   //环境服务器默认端口号
    private final int threadSize = 50;              //服务器默认线程数
    private final int maxBufferSize = 1000;          //默认最大流字节数

    private static final Logger _logger = LoggerFactory.getLogger(SlopeDTUService.class);

    public static final org.apache.logging.log4j.Logger logger = LogManager.getLogger("FileUtils");

    private ConcurrentHashMap<String, DeviceSocketChannel> deviceSocketMap = new ConcurrentHashMap<>();

    protected AsynchronousChannelGroup asynchronousChannelGroup;//线程池
    private AsynchronousServerSocketChannel serverChannel;//整个监听服务

    @Autowired
    private RedisDBService redisDBService;

    class DeviceSocketChannel {
        private AsynchronousSocketChannel channel;
        private Date updateTime;
        private int autoCloseTime = 3;

        public DeviceSocketChannel(AsynchronousSocketChannel channel, int autoCloseTime) {
            this.channel = channel;
            updateTime = new Date();
            this.autoCloseTime = autoCloseTime;
        }

        //断开Socket
        public void close() {
            try {
                this.channel.close();
            } catch (Exception e) {

            }
        }

        public void writeCommand(String cmd) {
            try {
                int length = cmd.length() * 2;
                ByteBuffer writeBuffer = ByteBuffer.allocate(length);
                writeBuffer.clear();
                writeBuffer.put(myStringUtils.hexStringToBytes(cmd));
                writeBuffer.flip();
                _logger.info("设备下发指令:" + cmd);
                channel.write(writeBuffer).get();

            } catch (Exception e) {
                e.printStackTrace();
                logger.error(e);
                throw new RuntimeException(e);
            }
        }
    }

    @Autowired
    private ISlopeGlareService slopeGlareService;

    @Autowired
    private SlopeGlareHandleService slopeGlareHandleService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        try {
            asynchronousChannelGroup = AsynchronousChannelGroup.withCachedThreadPool(Executors.newCachedThreadPool(), threadSize);
            serverChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup);
            serverChannel.bind(new InetSocketAddress(port));
            System.out.println("DTU设备服务启动成功，监听端口:" + port);
            serverChannel.accept(this, new CompletionHandler<AsynchronousSocketChannel, SlopeDTUService>() {
                @Override
                public void completed(AsynchronousSocketChannel result, SlopeDTUService attachment) {
                    try {
                        System.out.println("DTU设备连接到服务器,设备IP地址:" + result.getRemoteAddress().toString());
                        serverChannel.accept(attachment, this);       // 再次接收客户端连接
                        handleWithCompletionHandler(result);
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(e);
                        throw new RuntimeException(e);
                    }
                }

                @Override
                public void failed(Throwable exc, SlopeDTUService attachment) {
                    System.out.println("Received failed");
                    exc.printStackTrace();
                    logger.error(exc);
                    attachment.serverChannel.accept(attachment, this);
                    throw new RuntimeException(exc);
                }

            });

            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    for (String imei : deviceSocketMap.keySet()) {
                        try {
                            DeviceSocketChannel dsc = deviceSocketMap.get(imei);
                            if (!redisDBService.isDeviceOnline(imei)) {
                                closeDevice(imei);
                                slopeGlareHandleService.setGlareState(imei, 0);
                            } else {
                                String cmd = "DD66010500000149AA";
                                dsc.writeCommand(cmd);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            logger.error(e);
                            throw new RuntimeException(e);
                        }
                    }
                }
            }, 0, 60, TimeUnit.SECONDS);

        } catch (IOException e) {
            _logger.error(e.toString());
            logger.error(e);
        }
    }

    private void handleWithCompletionHandler(final AsynchronousSocketChannel channel) {
        try {
            final ByteBuffer buffer = ByteBuffer.allocate(maxBufferSize);
            final long timeout = -1;            //无限等待输入
            channel.read(buffer, timeout, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
                String imei;

                @Override
                public void completed(Integer result, Object attachment) {
                    if (result == -1) {//正常断开，断开soceket
                        try {
                            channel.close();
                            if (imei != null) {
                                _logger.info("设备" + imei + "断开");
                                deviceSocketMap.remove(imei);
                                slopeGlareHandleService.setGlareState(imei, 0);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                            logger.error(e);
                            throw new RuntimeException(e);
                        }
                        return;
                    }
                    buffer.flip();
                    String hexString = myStringUtils.BinaryToHexString(buffer.array());
                    hexString = hexString.replace(" ", "");
                    hexString = hexString.substring(0, result * 2);
                    _logger.info("收到DTU数据：" + hexString);
                    buffer.clear();
                    channel.read(buffer, timeout, TimeUnit.SECONDS, null, this);    //继续接收消息
                    handleMessage(hexString);
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                    exc.printStackTrace();
                    _logger.error(exc.toString());
                    logger.error(exc);
                    throw new RuntimeException(exc);
                }

                //消息处理
                private void handleMessage(String hexMsg) {
                    try {
                        //验证包
                        if (this.imei == null) {
                            if (hexMsg.length() == 16) {
                                String msg = new String(myStringUtils.hexStringToBytes(hexMsg));
                                this.imei = msg;
                                DeviceSocketChannel dsc = new DeviceSocketChannel(channel, 3);
                                if (slopeGlareHandleService.setGlareState(imei, 1)) {
                                    redisDBService.deviceHeartBeat(imei);
                                    deviceSocketMap.put(imei, dsc);
                                    _logger.info("设备" + imei + "上线");
                                } else {
                                    _logger.info("设备" + imei + "未录入系统或已停用");
                                    channel.close();
                                }
                            }
                        } else {
                            if (slopeGlareHandleService.setGlareState(imei, 1)) {
                                redisDBService.deviceHeartBeat(imei);
                            } else {
                                deviceSocketMap.remove(imei);
                                _logger.info("设备" + imei + "已停用");
                                channel.close();
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(e);
                        throw new RuntimeException(e);
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e);
            throw new RuntimeException(e);
        }
    }

    //控制告警设备
    public void controlAlarmDevice(String imei, boolean on) {
        Integer ctrlNum = 1;
        if (deviceSocketMap.containsKey(imei)) {
            DeviceSocketChannel dsc = deviceSocketMap.get(imei);
            String SwitchOn = "DD66" + myStringUtils.numToHex8(ctrlNum) + (on ? "0A" : "01") + "020F";
            String cmd = SwitchOn + myStringUtils.buildCrcEx(SwitchOn) + "AA";
            dsc.writeCommand(cmd);

            //更改数据库状态
            slopeGlareHandleService.setGlareSwitchState(imei, on);
        }
    }

    //关闭设备
    public void closeDevice(String imei) {
        if (deviceSocketMap.containsKey(imei)) {
            deviceSocketMap.get(imei).close();
            deviceSocketMap.remove(imei);
            redisDBService.clearDeviceRealtime(imei);
        }

    }

}
